package net.alche.fa.core.doing;

import java.util.List;
import net.alche.fa.core.doing.invoker.AbstractObjectInvoker;
import net.alche.fa.core.model.Logic;
import net.alche.fa.core.model.DebugFall;
import net.alche.fa.core.model.FaValue;
import net.alche.fa.core.CodeLevel;
import net.alche.fa.core.SageStone;
import net.alche.fa.core.FaException;
import net.alche.al.util.AC;
import net.alche.al.util.Far;
import net.alche.fa.core.FaPlatform;
import java.util.Map;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.ArrayList;
import net.alche.fa.core.doing.invoker.FatInvokerUtil;
import net.alche.fa.core.FaClass;
import java.lang.reflect.Method;
import java.util.Set;
import net.alche.al.util.alches.ToolClass;

//
// 点运算 .
//
@FaAnnoDoing
@SuppressWarnings("all")
public class DotDo extends AbstractDoing {
  static List<AbstractObjectInvoker> invokerList = null;

  public boolean matched(String what, Logic g) {
    return ".".equals(what);
  }

  public FaValue doing(Logic g, CodeLevel lvl, SageStone st) throws FaException {
    FaValue objVal = super.getLogicObjVal(st, lvl, g);
    if (lvl.logicParam != null || g.getLogicData() != null && "(".equals(g.getLogicData().getWhat())) {
      List<FaValue> params = getFaValueList(st, lvl, lvl.logicParam != null ? lvl.logicParam : g.getLogicData());
      String methodName = "";
      if (g.getLogicData() == null) {
        methodName = g.getData();
      } else {
        if (lvl.logicParam == null) {
          methodName = g.getLogicData().getObj();
        } else {
          FaValue dataVal = super.getLogicDataVal(st, lvl, g);
          methodName = dataVal != null ? dataVal.getStrValue() : "";
        }
      }
      try {
        FaValue fav = callMethod(this.plat, objVal, methodName, params, lvl);
        return fav;
      } catch (Exception ex) {
        if (ex instanceof FaException) {
          throw ex;
        } else {
          AC.log("[DotDo.doing] exceptin: " + methodName);
          ex.printStackTrace();
        }
      }
      return new FaValue();
    } else {
      return readField(objVal, g.getData(), lvl);
    }
  }

  public static FaValue dotDo(FaPlatform plat, FaValue objVal, String methodOrAttrName, List<FaValue> params,
      CodeLevel lvl) throws FaException {
    if (params != null) {
      FaValue fav = callMethod(plat, objVal, methodOrAttrName, params, lvl);
      return fav;
    } else {
      return readField(objVal, methodOrAttrName, lvl);
    }
  }

  //
  // 访问FaValue的方法
  //
  // @param objVal
  // @param fieldName
  // @return
  // @throws FaException
  //
  public static FaValue readField(FaValue objVal, String fieldName, CodeLevel lvl) throws FaException {
    // if objVal.isNull() && FaClassConf.classMap.containsKey(objVal.getNullName())
    // FaClassConf conf = FaClassConf.classMap.get(objVal.getNullName())
    // return conf.getStaticVal().get(fieldName)
    if (objVal.type == FaValue._SAGE) {
      try {
        return objVal.getSageValue().getVarData(fieldName, lvl);
      } catch (NullPointerException ex) {
        throw new FaException(FaException.NULL_ERROR, objVal.getVarName() + " is null , 无法读取" + fieldName + "");
      }
    } else if (objVal.type == FaValue._MAP) {
      Map map = (Map) objVal.getObjValue();
      return new FaValue(map.get(fieldName));
    }
    // FaValue fav = objVal.getMapValue().get(fieldName)
    // if fav == null
    // fav = new FaValue()
    // objVal.getMapValue().put(fieldName, fav)
    //
    // return fav
    else {
      FaValue fav = getBeanField(objVal, fieldName);
      return fav;
    }
  }

  public static FaValue getBeanField(FaValue obj, String colName) throws FaException {
    if (obj == null || obj.isNull()) {
      throw new FaException(FaException.NULL_ERROR, "obj is null | fieldName=" + obj.getVarName() + "." + colName);
    }
    try {
      Object ob = null;
      if (colName.equals("class")) {
        return new FaValue(obj.getValue().getClass(), FaValue._OBJECT);
      }
      try {
        if (obj.getType() == FaValue._ARR || obj.getValue().getClass().isArray()) {
          if (colName.equals("length")) {
            ob = Array.getLength(obj.getValue());
            return new FaValue(ob, FaValue._INT);
          }
        } else {
          Field fi = obj.getValue().getClass().getField(colName);
          ob = fi.get(obj.getValue());
        }
      } catch (IllegalAccessException e) {
        e.printStackTrace();
        throw new FaException(FaException.NO_SUCH_FIELD, colName);
      }
      if (ob != null && ob instanceof FaValue) {
        FaValue rb = (FaValue) ob;
        // if (rb.getObjValue() != null) {
        // if(rb.getObjValue() instanceof FaClass rbFlass){
        // rbFlass.setPlat(null);
        // }
        // }
        return rb;
      }
      return new FaValue(ob, FaValue._OBJECT);
    } catch (NoSuchFieldException e) {
      throw new FaException(FaException.NO_SUCH_FIELD, colName);
    } catch (SecurityException e) {
      e.printStackTrace();
      throw new FaException(FaException.NO_SUCH_FIELD, colName);
    }
  }

  public static FaValue callMethod(FaPlatform plat, FaValue obj, String methodName, List<FaValue> params, CodeLevel lvl)
      throws FaException {
    if (obj == null) {
      throw new FaException(FaException.NULL_ERROR, "dot.util callMethod=" + methodName);
    }
    if (obj.isNull()) {
      throw new FaException(FaException.NULL_ERROR,
          obj.getVarName() + " is null | method = " + obj.getVarName() + "." + methodName + "()",
          "params=" + AC.json.toStr(params));
    }
    if (params == null) {
      params = new ArrayList<>();
    }
    if (obj.getType() == FaValue._SAGE || obj.getSageValue() != null) {
      if (obj.getObjValue() instanceof FaValue) {
        FaValue favDeep = (FaValue) obj.getObjValue();
        return callSageMethod(favDeep.getSageValue(), methodName, params, lvl);
      } else if (obj.getObjValue() != null) { // && obj instanceof List
        return invoke(obj, methodName, params, true);
      } else {
        return callSageMethod(obj.getSageValue(), methodName, params, lvl);
      }
    } else if (obj.getObjValue() instanceof FaValue) {
      FaValue favDeep = (FaValue) obj.getObjValue();
      return callSageMethod(favDeep.getSageValue(), methodName, params, lvl);
    }
    // else if obj.getType() == FaValue._ARR
    // return callArrMethod(obj.getArrValue(), methodName, params)
    else {

      if (obj.getObjValue() instanceof FatInvokerUtil) {
        return FatInvokerUtil.fatDo(plat, obj, methodName, params);
      }
      if (obj.getObjValue() instanceof Far) {
        if (methodName.startsWith("log")) {
          String txt = getLogLineInfo(lvl);
          if (txt != null) {
            params.add(0, new FaValue(txt));
          }
        }
      }
      return invoke(obj, methodName, params, true);
    }
  }

  public static String getLogLineInfo(CodeLevel lvl) {
    if (lvl.debugFall != null) {
      DebugFall cur = lvl.debugFall;
      Integer num = cur.getNum();
      while (cur.getPar() != null) {
        DebugFall par = cur.getPar();
        if (par.getPrefix() != null) {
          return "[" + par.getPrefix() + "] - " + num;
        }
        num = lvl.debugFall.getNum();
        cur = cur.getPar();
      }
    }
    return null;
  }

  public static FaValue callArrMethod(FaValue[] arr, String mName, List<FaValue> params) throws FaException {
    FaValue first = null;
    if (params != null && !params.isEmpty()) {
      first = params.get(0);
    }
    switch (mName) {
      case "length": {
        return new FaValue(arr.length);
      }
      default: {
        throw new FaException(FaException.LESS_METHOD, "fa-length method not find | method = " + mName,
            "params=" + AC.json.toStr(params));
      }
    }
  }

  public static FaValue callSageMethod(SageStone st, String methodName, List<FaValue> params, CodeLevel lvl)
      throws FaException {
    if (st == null) {
      throw new FaException(FaException.NULL_ERROR, "obj is null | method = " + methodName,
          "params=" + AC.json.toStr(params));
    }
    FaClass fac = st.getFaClass();
    return fac.call(methodName, params, lvl);
  }

  public static FaValue invoke(FaValue fav, String methodName, List<FaValue> paramList, boolean isOValue)
      throws FaException {
    Object obj = fav.getValue();
    if (obj == null) {
      throw new FaException(fav.getVarName() + " is null", fav.getVarName() + "." + methodName + "()");
    }
    // 特别实现的方法
    if (DotDo.invokerList == null) {
      DotDo.initInvokerList();
    }
    for (AbstractObjectInvoker invoker : DotDo.invokerList) {
      if (invoker.matched(fav, obj, methodName, paramList)) {
        try {
          return invoker.invoke(fav, obj, methodName, paramList);
        } catch (Exception ex) {
          ex.printStackTrace();
          throw new FaException("0", "[DotDo.invoke]" + methodName);
        }
      }
    }
    // 参数类型集合
    Class[] classes = new Class[] {};
    Object[] params = null;
    FaValue ov = new FaValue();
    if (paramList != null && !paramList.isEmpty()) {
      classes = new Class[paramList.size()];
      params = new Object[paramList.size()];
      for (int i = 0; i < paramList.size(); i++) {
        classes[i] = paramList.get(i).getValueClass();
        params[i] = paramList.get(i).getValue();
      }
    }
    Method method = null;
    try {
      method = obj.getClass().getMethod(methodName, classes);
    } catch (Exception ex) {
    }
    // ex.printStackTrace()
    if (method == null) {
      method = DotDo.findMethod(obj, methodName, classes);
    }
    if (method != null) {
      try {
        method.setAccessible(true);
      } catch (Exception ex) {
        ex.printStackTrace();
      }
      try {
        Object backObj = null;
        // 可变参数
        Object dynObj = isDynamicParam(method, params);
        if (dynObj != null) {
          backObj = method.invoke(obj, dynObj);
        } else {
          backObj = method.invoke(obj, params);
        }
        if (backObj != null && backObj instanceof FaValue) {
          return (FaValue) backObj;
        } else {
          ov.aiData(backObj);
        }
      } catch (Exception ex) {
        throw new FaException(ex);
      }
    } else {
      throw new FaException(FaException.LESS_METHOD, "[" + methodName + "]", "" + obj);
    }
    return ov;
  }

  public static Object isDynamicParam(Method method, Object[] params) {
    if (method.getParameterCount() == 1) {
      Class cls = method.getParameterTypes()[0];
      if (cls.equals(Object[].class)) {
        return (Object) params;
      }
      if (cls.equals(String[].class)) {
        String[] strs = new String[params.length];
        int i = 0;
        for (Object obj : params) {
          strs[i++] = (String) obj;
        }
        return strs;
      }
      if (cls.equals(Integer[].class)) {
        Integer[] strs = new Integer[params.length];
        int i = 0;
        for (Object obj : params) {
          strs[i++] = (Integer) obj;
        }
        return strs;
      }
    }
    return null;
  }

  public static Method findMethod(Object obj, String methodName, Class[] classes) {
    Method[] methods = obj.getClass().getMethods();
    Method cur = null;
    Method curMin = null;
    int like = 0;
    for (Method method : methods) {
      if (methodName.equals(method.getName())) {
        if (method.getParameterCount() == classes.length) {
          if (cur == null) {
            cur = method;
          }
          int likeValue = compareParamsValue(method.getParameterTypes(), classes);
          if (likeValue > like) {
            cur = method;
            like = likeValue;
          }
        } else {
          curMin = method;
        }
      }
    }
    if (cur == null) {
      return curMin;
    }
    return cur;
  }

  public static int compareParamsValue(Class[] params, Class[] values) {
    int i = 0;
    int like = 0;
    for (Class cl : params) {
      Class val = values[i++];
      if (val.equals(cl)) {
        like += 50;
      } else if (cl.isInstance(val)) {
        like += 10;
      } else {
        // like = 0
        break;
      }
    }
    return like;
  }

  private static void initInvokerList() {
    DotDo.invokerList = new ArrayList<>();
    try {
      Set<Class<?>> classes = ToolClass.getClassList("net.alche.fa.core.doing.invoker", FaAnnoDoing.class);
      for (Class<?> cla : classes) {
        AbstractObjectInvoker ad = (AbstractObjectInvoker) cla.getDeclaredConstructor().newInstance();
        DotDo.invokerList.add(ad);
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}